/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.solr.util.hll;

/** A collection of utilities to work with numbers. */
class NumberUtil {
  // loge(2) (log-base e of 2)
  public static final double LOGE_2 = 0.6931471805599453;

  // ************************************************************************
  /**
   * Computes the <code>log2</code> (log-base-two) of the specified value.
   *
   * @param value the <code>double</code> for which the <code>log2</code> is desired.
   * @return the <code>log2</code> of the specified value
   */
  public static double log2(final double value) {
    // REF:  http://en.wikipedia.org/wiki/Logarithmic_scale (conversion of bases)
    return Math.log(value) / LOGE_2;
  }

  // ========================================================================
  // the hex characters
  private static final char[] HEX = {
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
  };

  // ------------------------------------------------------------------------
  /**
   * Converts the specified array of <code>byte</code>s into a string of hex characters (low <code>
   * byte</code> first).
   *
   * @param bytes the array of <code>byte</code>s that are to be converted. This cannot be <code>
   *     null</code> though it may be empty.
   * @param offset the offset in <code>bytes</code> at which the bytes will be taken. This cannot be
   *     negative and must be less than <code>bytes.length - 1</code>.
   * @param count the number of bytes to be retrieved from the specified array. This cannot be
   *     negative. If greater than <code>bytes.length - offset</code> then that value is used.
   * @return a string of at most <code>count</code> characters that represents the specified byte
   *     array in hex. This will never be <code>null</code> though it may be empty if <code>bytes
   *     </code> is empty or <code>count</code> is zero.
   * @throws IllegalArgumentException if <code>offset</code> is greater than or equal to <code>
   *     bytes.length</code>.
   * @see #fromHex(String, int, int)
   */
  public static String toHex(final byte[] bytes, final int offset, final int count) {
    if (offset >= bytes.length)
      throw new IllegalArgumentException(
          "Offset is greater than the length ("
              + offset
              + " >= "
              + bytes.length
              + ").") /*by contract*/;
    final int byteCount = Math.min((bytes.length - offset), count);
    final int upperBound = byteCount + offset;

    final char[] chars = new char[byteCount * 2 /*two chars per byte*/];
    int charIndex = 0;
    for (int i = offset; i < upperBound; i++) {
      final byte value = bytes[i];
      chars[charIndex++] = HEX[(value >>> 4) & 0x0F];
      chars[charIndex++] = HEX[value & 0x0F];
    }

    return new String(chars);
  }

  /**
   * Converts the specified array of hex characters into an array of <code>byte</code>s (low <code>
   * byte</code> first).
   *
   * @param string the string of hex characters to be converted into <code>byte</code>s. This cannot
   *     be <code>null</code> though it may be blank.
   * @param offset the offset in the string at which the characters will be taken. This cannot be
   *     negative and must be less than <code>string.length() - 1</code>.
   * @param count the number of characters to be retrieved from the specified string. This cannot be
   *     negative and must be divisible by two (since there are two characters per <code>byte</code>
   *     ).
   * @return the array of <code>byte</code>s that were converted from the specified string (in the
   *     specified range). This will never be <code>null</code> though it may be empty if <code>
   *     string</code> is empty or <code>count</code> is zero.
   * @throws IllegalArgumentException if <code>offset</code> is greater than or equal to <code>
   *     string.length()</code> or if <code>count</code> is not divisible by two.
   * @see #toHex(byte[], int, int)
   */
  public static byte[] fromHex(final String string, final int offset, final int count) {
    if (offset >= string.length())
      throw new IllegalArgumentException(
          "Offset is greater than the length ("
              + offset
              + " >= "
              + string.length()
              + ").") /*by contract*/;
    if ((count & 0x01) != 0)
      throw new IllegalArgumentException(
          "Count is not divisible by two (" + count + ").") /*by contract*/;
    final int charCount = Math.min((string.length() - offset), count);
    final int upperBound = offset + charCount;

    final byte[] bytes = new byte[charCount >>> 1 /*aka /2*/];
    int byteIndex = 0 /*beginning*/;
    for (int i = offset; i < upperBound; i += 2) {
      bytes[byteIndex++] =
          (byte) (((digit(string.charAt(i)) << 4) | digit(string.charAt(i + 1))) & 0xFF);
    }

    return bytes;
  }

  // ------------------------------------------------------------------------
  /**
   * @param character a hex character to be converted to a <code>byte</code>. This cannot be a
   *     character other than [a-fA-F0-9].
   * @return the value of the specified character. This will be a value <code>0</code> through
   *     <code>15</code>.
   * @throws IllegalArgumentException if the specified character is not in [a-fA-F0-9]
   */
  private static final int digit(final char character) {
    switch (character) {
      case '0':
        return 0;
      case '1':
        return 1;
      case '2':
        return 2;
      case '3':
        return 3;
      case '4':
        return 4;
      case '5':
        return 5;
      case '6':
        return 6;
      case '7':
        return 7;
      case '8':
        return 8;
      case '9':
        return 9;
      case 'a':
      case 'A':
        return 10;
      case 'b':
      case 'B':
        return 11;
      case 'c':
      case 'C':
        return 12;
      case 'd':
      case 'D':
        return 13;
      case 'e':
      case 'E':
        return 14;
      case 'f':
      case 'F':
        return 15;

      default:
        throw new IllegalArgumentException(
            "Character is not in [a-fA-F0-9] ('" + character + "').");
    }
  }
}
